﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Orchard.Data;
using Bootstrap.Controllers;
using Bootstrap.Models;

namespace Bootstrap.Services
{
    public class BootstrapSettingsService : IBootstrapSettingsService {
        
        private readonly IDictionary<string, FeatureDescriptor> _availableFeatures;
        private readonly IDictionary<string, VariableDescriptor> _availableVariables;
        private readonly IDictionary<string, SettingDescriptor> _availableSettings; 

        private readonly IEnumerable<string> _required; 

        private readonly IDictionary<string, FeatureDefaultDescriptor> _featureDefaults;
        private readonly IDictionary<string, VariableDefaultDescriptor> _variableDefaults;
        private readonly IDictionary<string, SettingDefaultDescriptor> _settingDefaults;
 
        private readonly IDictionary<string, BootstrapFeatureRecord> _featureSettings;
        private readonly IDictionary<string, BootstrapVariableRecord> _variableSettings;
        private readonly IDictionary<string, BootstrapSettingRecord> _settings; 

        private readonly IRepository<BootstrapFeatureRecord> _featureRepository;
        private readonly IRepository<BootstrapVariableRecord> _variableRepository;
        private readonly IRepository<BootstrapSettingRecord> _settingRepository;
 
        public BootstrapSettingsService(
            IRepository<BootstrapFeatureRecord> featureRepository,
            IRepository<BootstrapVariableRecord> variableRepository,
            IRepository<BootstrapSettingRecord> settingRepository, 
            IEnumerable<IBootstrapFeature> availableFeatures,
            IEnumerable<IBootstrapVariable> availableVariables,
            IEnumerable<IBootstrapSetting> availableSettings,
            IEnumerable<IBootstrapDefaults> availableDefaults
            ) {
            _featureRepository = featureRepository;
            _variableRepository = variableRepository;
            _settingRepository = settingRepository;

            var defaults = new BootstrapDefaultSettingsContext();
            availableDefaults
                .OrderBy(x => x.Priority)
                .ToList()
                .ForEach(
                    x => x.Define(defaults)
                );


            _featureDefaults =
                
                defaults.FeatureState.ToDictionary(
                    x => x.Key,
                    x => x.Value(new FeatureDefaultDescriptor() { Name = x.Key })
                );

            _variableDefaults =
                defaults.VariableValues
                    .ToDictionary(
                        x => x.Key,
                        x => x.Value(new VariableDefaultDescriptor() { Name = x.Key })
                    );
            _settingDefaults =
                defaults.SettingValues
                    .ToDictionary(
                        x => x.Key,
                        x => x.Value(new SettingDefaultDescriptor() { Name = x.Key })
                    );

            var features = new FeatureDescriptionContext();
            availableFeatures
                .ToList()
                .ForEach(
                    x => x.Describe(features)
                );

            _availableFeatures = features.Features.ToDictionary(
                x => x.Key,
                x => x.Value(new FeatureDescriptor() { Name = x.Key})
            );

            var variables = new VariableDescriptionContext();
            availableVariables
                .ToList()
                .ForEach(
                    x => x.Describe(variables)
                );

            _availableVariables = variables.Variables.ToDictionary(
                x => x.Key,
                x => x.Value(new VariableDescriptor() { Name = x.Key })
            );

            var settings = new SettingDescriptionContext();
            availableSettings
                .ToList()
                .ForEach(
                    x => x.Describe(settings)
                );
            _availableSettings = settings.Settings.ToDictionary(
                x => x.Key,
                x => x.Value(new SettingDescriptor() { Name = x.Key})
            );

            _required =
                _featureDefaults
                    .Where(x => x.Value.IsRequired)
                    .Select(x => x.Key)
                    .Join(
                        _availableFeatures
                            .SelectMany(x => x.Value.RequiredFeatures)
                            .ToArray(),
                        x => x, 
                        y => y,
                        (x, y) => x
            );

            var featureNames = _availableFeatures.Keys;
            _featureSettings =
                featureRepository
                    .Fetch(x => featureNames.Contains(x.Name))
                    .ToDictionary(
                        x => x.Name,
                        x => x
                    );
            var varNames = _availableVariables.Keys;
            _variableSettings =
                variableRepository
                    .Fetch(x => varNames.Contains(x.Name))
                    .ToDictionary(
                        x => x.Name,
                        x => x
                    );

            var settingNames = _availableSettings.Keys;
            _settings =
                _settingRepository
                    .Fetch(x => settingNames.Contains(x.Name))
                    .ToDictionary(
                        x => x.Name,
                        x => x
                    );

        }

        public IEnumerable<FeatureDescriptor> Features() {
            return _availableFeatures.Select(x => x.Value);
        }
        public IEnumerable<VariableDescriptor> Variables() {
            return _availableVariables.Select(x => x.Value);
        }
        public IEnumerable<SettingDescriptor> Settings() {
            return _availableSettings.Select(x => x.Value);
        }

        public bool IsEnabled(string name) {
            return
                (_featureDefaults.ContainsKey(name) && _featureDefaults[name].IsEnabled) ||
                (_featureSettings.ContainsKey(name) && _featureSettings[name].Enabled) ||
                _required.Contains(name);
        }
        public bool SetFeature(string name, bool enabled) {
            if (_featureDefaults.ContainsKey(name) && enabled == _featureDefaults[name].IsEnabled) {
                if (_featureSettings.ContainsKey(name))
                    _featureRepository.Delete(_featureSettings[name]);
                return true;
            }

            if (_featureSettings.ContainsKey(name))
            {
                var feature = _featureSettings[name];
                feature.Enabled = enabled;
                _featureRepository.Update(feature);
            }
            else
            {
                var feature =
                    new BootstrapFeatureRecord()
                    {
                        Name = name,
                        Enabled = enabled
                    };
                _featureRepository.Create(feature);
                _featureSettings.Add(
                    name,
                    feature
                );
            }
            return true;
                
        }
        public int GetPosition(string name) {
            return 
                _featureDefaults.ContainsKey(name) ? 
                _featureDefaults[name].RenderPosition : 
                0;
        }

        public string GetValue(string name) {
            return 
                _variableSettings.ContainsKey(name) ?
                _variableSettings[name].Value :
                _variableDefaults.ContainsKey(name) ? 
                _variableDefaults[name].Value : 
                null;
        }
        public bool SetValue(string name, string value) {
            if (_variableDefaults.ContainsKey(name) && (value == _variableDefaults[name].Value || value == ""))
            {
                if (_variableSettings.ContainsKey(name))
                    _variableRepository.Delete(_variableSettings[name]);
                return true;
            }
            
            if (_variableSettings.ContainsKey(name))
            {
                var variable = _variableSettings[name];
                variable.Value = value;
                _variableRepository.Update(variable);
            }
            else {
                var variable = 
                    new BootstrapVariableRecord() {
                        Name = name,
                        Value = value
                    };
                _variableRepository.Create(variable);
                _variableSettings.Add(
                    name,
                    variable
                );
            }
            return true;
        }

        public string GetSetting(string name) {
            return
                _settings.ContainsKey(name) ?
                _settings[name].Value :
                _settingDefaults.ContainsKey(name) ?
                _settingDefaults[name].Value :
                null;
        }
        public bool SetSetting(string name, string value)
        {
            if (_settingDefaults.ContainsKey(name) && (value == _settingDefaults[name].Value || value == ""))
            {
                if (_settings.ContainsKey(name))
                    _settingRepository.Delete(_settings[name]);
                return true;
            }

            if (_settings.ContainsKey(name))
            {
                var setting = _settings[name];
                setting.Value = value;
                _settingRepository.Update(setting);
            }
            else
            {
                var setting =
                    new BootstrapSettingRecord()
                    {
                        Name = name,
                        Value = value
                    };
                _settingRepository.Create(setting);
                _settings.Add(
                    name,
                    setting
                );
            }
            return true;
        }
        
        public IEnumerable<FeatureDescriptor> EnabledFeatures() {
            return
                _availableFeatures
                    .Where(x => IsEnabled(x.Key))
                    .Select(x => x.Value);
        }
        public IDictionary<string, string> AvailableVariables() {
            return
                _availableVariables
                    .OrderByDescending(x => x.Value.Section.Priority)
                    .ThenByDescending(x => x.Value.DisplayPriority)
                    .ToDictionary(
                        x => x.Key,
                        x => GetValue(x.Key)
                    )
                    .Where(x => x.Value != null)
                    .ToDictionary(
                        x => x.Key,
                        x => x.Value
                    );
        }
        public IDictionary<string, string> AvailableSettings() {
            return
                _availableSettings
                    .OrderByDescending(x => x.Value.Category.Priority)
                    .ThenByDescending(x => x.Value.DisplayPriority)
                    .ToDictionary(x => x.Key, x => GetSetting(x.Key))
                    .Where(x => x.Value != null)
                    .ToDictionary(
                        x => x.Key,
                        x => x.Value
                    );
        } 

        public bool RestoreFeatureDefaults() {
            foreach (
                var feature 
                in _availableFeatures
                    .Where(
                        feature => 
                            _featureDefaults.ContainsKey(feature.Key) && 
                            _featureSettings.ContainsKey(feature.Key)
                    )
                ) 
            {
                _featureRepository.Delete(_featureSettings[feature.Key]);
            }
            return true;
        }
        public bool RestoreVariableDefaults()
        {
            foreach (
                var variable
                in _availableVariables
                    .Where(
                        variable =>
                            _variableDefaults.ContainsKey(variable.Key) &&
                            _variableSettings.ContainsKey(variable.Key)
                    )
                )
            {
                _variableRepository.Delete(_variableSettings[variable.Key]);
            }
            return true;
        }
        public bool RestoreSettingDefaults()
        {
            foreach (
                var setting
                in _availableSettings
                    .Where(
                        setting =>
                            _settingDefaults.ContainsKey(setting.Key) &&
                            _settings.ContainsKey(setting.Key)
                    )
                )
            {
                _settingRepository.Delete(_settings[setting.Key]);
            }
            return true;
        }

        public XDocument Export() {
            var doc = XDocument.Parse("<bootstrap/>");

            var settings = new XElement("settings");
            foreach (var setting in Settings()) {
                var val = GetSetting(setting.Name);
                settings.Add(
                    new XElement(
                        "setting", 
                        new XAttribute("name", setting.Name),
                        new XAttribute("value", val))
                );
            }
            if (doc.Root != null) {
                doc.Root.Add(settings);
            }

            var features = new XElement("features");
            foreach (var feature in Features())
            {
                var val = IsEnabled(feature.Name);
                features.Add(
                    new XElement(
                        "feature",
                        new XAttribute("name", feature.Name),
                        new XAttribute("enabled", val.ToString(CultureInfo.InvariantCulture)))
                );
            }
            if (doc.Root != null) {
                doc.Root.Add(features);
            }

            var variables = new XElement("variables");
            foreach (var variable in Variables())
            {
                var val = GetValue(variable.Name);
                variables.Add(
                    new XElement(
                        "variable",
                        new XAttribute("name", variable.Name),
                        new XAttribute("value", val.ToString(CultureInfo.InvariantCulture)))
                );
            }
            if (doc.Root != null) {
                doc.Root.Add(variables);
            }

            return doc;
        }

        public bool Import(XDocument source) {
            foreach (var setting in source.Descendants("setting")) {
                var attribute = setting.Attribute("name");
                if (attribute != null) {
                    var name = attribute.Value;
                    var vAttribute = setting.Attribute("value");
                    if (vAttribute != null) {
                        var value = vAttribute.Value;
                        SetSetting(name, value);
                    }
                }
            }

            foreach (var feature in source.Descendants("feature")) {
                var attribute = feature.Attribute("name");
                if (attribute != null) {
                    var name = attribute.Value;
                    var vAttribute = feature.Attribute("enabled");
                    if (vAttribute != null) {
                        var value = vAttribute.Value;
                        SetFeature(name, bool.Parse(value));
                    }
                }
            }

            foreach (var variable in source.Descendants("variable")) {
                var attribute = variable.Attribute("name");
                if (attribute != null) {
                    var name = attribute.Value;
                    var vAttribute = variable.Attribute("value");
                    if (vAttribute != null) {
                        var value = vAttribute.Value;
                        SetValue(name, value);
                    }
                }
            }

            return true;
        }
    }
}
